package org.rcgwt.client.ui.calendar.renderer.impl;

import java.util.Date;

import org.rcgwt.client.theme.CssStyle;
import org.rcgwt.client.ui.calendar.controller.CalendarAction;
import org.rcgwt.client.ui.calendar.controller.CalendarChangesMask;
import org.rcgwt.client.ui.calendar.controller.CalendarController;
import org.rcgwt.client.ui.calendar.event.ModelChangedEvent;
import org.rcgwt.client.ui.calendar.event.ModelChangedHandler;
import org.rcgwt.client.ui.calendar.i18n.CalendarUtils;
import org.rcgwt.client.ui.calendar.model.impl.CalendarModel;
import org.rcgwt.client.ui.calendar.model.impl.SelectionType;
import org.rcgwt.client.ui.calendar.mvc.ICalendarContext;
import org.rcgwt.client.ui.calendar.renderer.IHeaderRenderer;
import org.rcgwt.client.ui.themes.ITheme;

import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyDownEvent;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.MouseWheelEvent;
import com.google.gwt.event.logical.shared.CloseEvent;
import com.google.gwt.event.logical.shared.CloseHandler;
import com.google.gwt.user.client.ui.FlexTable;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.Cell;

@SuppressWarnings("deprecation")
public class MediumCalendarHeaderRenderer implements IHeaderRenderer {

	/* Constants */
	private static final String[] monthes = CalendarUtils.getMonths();
	private static CssStyle COLLAPSED_STYLE;
	private static CssStyle OPENED_STYLE;

	private static final int LAST_DISPLAYED_YEAR_ROW = 5;

	private ITheme theme;
	private CalendarController controller;
	private CalendarModel model;

	private FlexTable mediumPanel;
	private PopupPanel popup;

	private Label l_year;
	private Label l_month;
	private Label arrowImage;

	/* Popup content fields */

	private int prevMonthRow;
	private int prevMonthCell;
	private int prevYearRow;
	private int prevYearCell;

	/* Grids */

	private Grid yearGrid;
	private Grid monthGrid;

	@Override
	public Widget render(ICalendarContext context) {

		theme = context.getTheme();
		controller = context.getController();
		model = context.getModel();

		/* Arrow Button CssStyles */
		COLLAPSED_STYLE = theme.getArrowButtonStyle();
		OPENED_STYLE = theme.getOpenedArrowButtonStyle();

		/* Controls Management */
		popup = new PopupPanel(true);
		popup.setAnimationEnabled(true);
		popup.setAutoHideEnabled(true);
		popup.addStyleName(theme.getPopupPanelStyle().getClassName());

		mediumPanel = new FlexTable();
		mediumPanel.setStyleName(theme.getMediumYearAndMonthPanelStyle()
				.getClassName());

		arrowImage = new Label();
		arrowImage.setStyleName(COLLAPSED_STYLE.getClassName());

		l_year = new Label(String.valueOf(context.getModel().getCurrentYear()));
		l_year.setStyleName(theme.getYearLabelStyle().getClassName());
		l_month = new Label(context.getModel().getCurrentMonthName());
		l_month.setStyleName(theme.getMonthLabelStyle().getClassName());

		popup.setWidget(createPopupContent());
		popup.setVisible(false);

		mediumPanel.setWidget(0, 0, l_year);
		mediumPanel.setWidget(1, 1, arrowImage);
		mediumPanel.setWidget(1, 0, l_month);

		createHandlers(context);
		return mediumPanel;
	}

	private void createHandlers(final ICalendarContext context) {
		arrowImage.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				int year = model.getDisplayedYear();
				for (int i = 0; i < 12; i++) {
					int row = getRowForMonth(i);
					int col = getColForMonth(i);
					if (controller.isDisplayableMin(i, year, 0) == false
							|| controller.isDisplayableMax(i, year, 0) == false) {
						monthGrid.getWidget(row, col).addStyleName(
								theme.getForbiddenStyle().getClassName());
					} else {
						monthGrid.getWidget(row, col).setStyleName(
								theme.getMonthLabelStyle().getClassName());
					}
				}
				int startYear = Integer.parseInt(((Label) yearGrid.getWidget(0,
						0)).getText());
				int displayableMonth = 0;
				for (int j = startYear; j < startYear + 12; j++) {
					int row = getRowForYear(j);
					int col = getCellForYear(j);
					displayableMonth = 0;
					if (row == -1 || col == -1) {
						continue;
					}
					for (int i = 0; i < 12; i++) {
						if (controller.isDisplayableMin(i, j, 0) == true
								&& controller.isDisplayableMax(i, j, 0) == true) {
							displayableMonth++;
						}
					}
					if (displayableMonth == 0) {
						yearGrid.getWidget(row, col).addStyleName(
								theme.getForbiddenStyle().getClassName());
					}
				}
				if (popup.isVisible() == true) {
					popup.setVisible(false);
					arrowImage.setStyleName(COLLAPSED_STYLE.getClassName());
				} else {
					popup.setVisible(true);
					popup.showRelativeTo(arrowImage);
					arrowImage.setStyleName(OPENED_STYLE.getClassName());
				}
			}
		});
		l_year.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				controller.handle(CalendarAction.SELECT_YEAR_CURSOR);
				controller.setSelectionType(SelectionType.YEAR);
			}
		});

		l_month.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				controller.handle(CalendarAction.SELECT_MONTH_CURSOR);
				controller.setSelectionType(SelectionType.MONTH);
			}
		});

		popup.addCloseHandler(new CloseHandler<PopupPanel>() {
			@Override
			public void onClose(CloseEvent<PopupPanel> event) {
				popup.setVisible(false);
				arrowImage.setStyleName(COLLAPSED_STYLE.getClassName());
			}
		});

		controller.addModelChangedHandler(new ModelChangedHandler() {
			@Override
			public void onModelChangedEvent(ModelChangedEvent event) {
				int type = event.getElementType();
				/* Selection / Deselection (It doesn't require date) */
				if ((type & CalendarChangesMask.SELECT_MONTH) > 0) {
					if (isSelected(l_year) == true) {
						deselectYear();
					}
					selectMonth();
				}
				if ((type & CalendarChangesMask.UNSELECT_MONTH) > 0) {
					deselectMonth();
				}
				if ((type & CalendarChangesMask.SELECT_YEAR) > 0) {
					if (isSelected(l_month) == true) {
						deselectMonth();
					}
					selectYear();
				}
				if ((type & CalendarChangesMask.UNSELECT_YEAR) > 0) {
					deselectYear();
				}
				Date d = event.getDate();
				if (d == null) {
					return;
				}
				int year = d.getYear() + 1900;
				int month = d.getMonth();
				/* HEADER ACTIONS */
				if ((event.getElementType() & CalendarChangesMask.SPECIFIED_YEAR) > 0) {
					l_year.setText(String.valueOf(year));
				}
				if ((type & CalendarChangesMask.SPECIFIED_MONTH) > 0) {
					l_month.setText(monthes[month]);
				}
				if ((type & (CalendarChangesMask.SPECIFIED_YEAR | CalendarChangesMask.SPECIFIED_MONTH)) > 0) {
					l_year.setText(String.valueOf(year));
					l_month.setText(monthes[month]);
				}
				/* FOOTER ACTION */
				if ((type & CalendarChangesMask.CURRENT_DAY) > 0) {
					l_year.setText(String.valueOf(year));
					l_month.setText(monthes[month]);
				}
			}
		});
		yearGrid.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				Cell c = yearGrid.getCellForEvent(event);
				if (c == null) {
					return;
				}
				int row = c.getRowIndex();
				int cell = c.getCellIndex();
				unselectYearCell(prevYearRow, prevYearCell);
				if (row > LAST_DISPLAYED_YEAR_ROW) {
					return;
				}
				prevYearRow = row;
				prevYearCell = cell;

				int year = getYearNumber(row, cell);
				if (controller.setDisplayedYear(year) == true) {
					selectYearCell(row, cell);
					controller.setSelectionType(SelectionType.YEAR);
					controller.handle(CalendarAction.SELECTION_TYPE);
					controller.handle(CalendarAction.REFRESH_CALENDAR);
					popup.hide();
				}
			}
		});
		monthGrid.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				Cell c = monthGrid.getCellForEvent(event);
				if (c == null) {
					return;
				}
				unselectMonthCell(prevMonthRow, prevMonthCell);
				int row = c.getRowIndex();
				int cell = c.getCellIndex();
				prevMonthRow = row;
				prevMonthCell = cell;
				int month = getMonthNumber(row, cell);
				if (controller.setDisplayedMonth(month) == true) {
					selectMonthCell(row, cell);
					controller.setSelectionType(SelectionType.MONTH);
					controller.handle(CalendarAction.SELECTION_TYPE);
					controller.handle(CalendarAction.REFRESH_CALENDAR);
					popup.hide();
				}
			}
		});
	}

	protected Widget createPopupContent() {
		HorizontalPanel rootPanel = new HorizontalPanel();
		int displayedMonth = model.getDisplayedMonth();
		int displayedYear = model.getDisplayedYear();

		monthGrid = new Grid(6, 2);
		monthGrid.addStyleName(theme.getPopupMonthGridStyle().getClassName());

		yearGrid = new Grid(7, 2);
		yearGrid.addStyleName(theme.getPopupYearGridStyle().getClassName());

		rootPanel.add(monthGrid);
		rootPanel.add(yearGrid);

		fillGrid();

		prevMonthRow = getRowForMonth(displayedMonth);
		prevMonthCell = getColForMonth(displayedMonth);

		prevYearRow = getRowForYear(displayedYear);
		prevYearCell = getCellForYear(displayedYear);

		return rootPanel;
	}

	private boolean isSelected(Widget w) {
		return w.getStyleName().contains(
				theme.getSelectedYearMonthLabelStyle().getClassName());
	}

	private void selectYear() {
		l_year.addStyleName(theme.getSelectedYearMonthLabelStyle()
				.getClassName());
	}

	private void deselectYear() {
		l_year.removeStyleName(theme.getSelectedYearMonthLabelStyle()
				.getClassName());
	}

	private void selectMonth() {
		l_month.addStyleName(theme.getSelectedYearMonthLabelStyle()
				.getClassName());
	}

	private void deselectMonth() {
		l_month.removeStyleName(theme.getSelectedYearMonthLabelStyle()
				.getClassName());
	}

	private void fillGrid() {
		int month = model.getDisplayedMonth();
		int year = model.getDisplayedYear();

		int row = 0;
		int col = 0;

		for (int i = 0; i < CalendarUtils.getShortMonths().length; i++) {
			String m = CalendarUtils.getShortMonths()[i];
			Label l = new Label(m);
			if (month == i) {
				l.addStyleName(theme.getSelectedYearMonthLabelStyle()
						.getClassName());
			} else {
				l.addStyleName(theme.getMonthLabelStyle().getClassName());
			}
			monthGrid.setWidget(row, col, l);
			if (row < LAST_DISPLAYED_YEAR_ROW) {
				row++;
			} else {
				row = 0;
				col = 1;
			}
		}
		int startYear = year - LAST_DISPLAYED_YEAR_ROW;
		int endYear = year + 6;

		fillYearGrid(startYear, endYear);

		Label b_prev = new Label();
		b_prev.addStyleName(theme.getPreviousButtonStyle().getClassName());
		Label b_next = new Label();
		b_next.addStyleName(theme.getNextButtonStyle().getClassName());

		b_prev.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				Label l = (Label) yearGrid.getWidget(0, 0);
				if (l == null) {
					return;
				}
				clearYearSelection();
				int first = Integer.parseInt(l.getText());
				fillYearGrid(first - 12, first - 1);
			}
		});
		b_next.addClickHandler(new ClickHandler() {
			@Override
			public void onClick(ClickEvent event) {
				Label l = (Label) yearGrid
						.getWidget(LAST_DISPLAYED_YEAR_ROW, 1);
				if (l == null) {
					return;
				}
				clearYearSelection();
				int last = Integer.parseInt(l.getText());
				fillYearGrid(last + 1, last + 12);
			}
		});

		yearGrid.setWidget(6, 0, b_prev);
		yearGrid.setWidget(6, 1, b_next);
	}

	private void fillYearGrid(int begin, int end) {
		int row = 0;
		int col = 0;
		for (int i = begin; i <= end; i++) {
			Label l = (Label) yearGrid.getWidget(row, col);
			if (l == null) {
				l = new Label();
				yearGrid.setWidget(row, col, l);
			}
			l.setText("" + i);
			if (i == model.getDisplayedYear()) {
				l.addStyleName(theme.getSelectedYearMonthLabelStyle()
						.getClassName());
			} else {
				l.addStyleName(theme.getMonthLabelStyle().getClassName());
			}
			if (row < LAST_DISPLAYED_YEAR_ROW) {
				row++;
			} else {
				row = 0;
				col = 1;
			}
		}
	}

	private int getMonthNumber(int row, int col) {
		if (col == 0) {
			return row;
		} else if (col == 1) {
			return 6 + row;
		}
		return -1;
	}

	private int getYearNumber(int row, int col) {
		if (row == yearGrid.getRowCount() - 1) {
			return -1;
		}
		Label l = (Label) yearGrid.getWidget(row, col);
		return Integer.parseInt(l.getText());
	}

	private int getRowForMonth(int month) {
		if (month < 6) {
			return month;
		} else {
			return month - 6;
		}
	}

	private int getColForMonth(int month) {
		if (month < 6) {
			return 0;
		} else {
			return 1;
		}
	}

	private int getRowForYear(int year) {
		for (int i = 0; i < yearGrid.getRowCount() - 1; i++) {
			for (int j = 0; j < yearGrid.getColumnCount(); j++) {
				if (i > LAST_DISPLAYED_YEAR_ROW) {
					continue;
				}
				Label l = (Label) yearGrid.getWidget(i, j);
				if (l == null) {
					continue;
				}
				if (Integer.parseInt(l.getText()) == year) {
					return i;
				}
			}
		}
		return -1;
	}

	private int getCellForYear(int year) {
		for (int i = 0; i < yearGrid.getRowCount() - 1; i++) {
			for (int j = 0; j < yearGrid.getColumnCount(); j++) {
				Label l = (Label) yearGrid.getWidget(i, j);
				if (l == null) {
					continue;
				}
				if (Integer.parseInt(l.getText()) == year) {
					return j;
				}
			}
		}
		return -1;
	}

	@Override
	public void update(ICalendarContext context) {
	}

	private void selectMonthCell(int row, int col) {
		Label l = (Label) monthGrid.getWidget(row, col);
		l.addStyleName(theme.getSelectedYearMonthLabelStyle().getClassName());
	}

	private void selectYearCell(int row, int col) {
		if (row > LAST_DISPLAYED_YEAR_ROW) {
			return;
		}
		Label l = (Label) yearGrid.getWidget(row, col);
		l.addStyleName(theme.getSelectedYearMonthLabelStyle().getClassName());
	}

	private void unselectMonthCell(int row, int col) {
		Label l = (Label) monthGrid.getWidget(row, col);
		l
				.removeStyleName(theme.getSelectedYearMonthLabelStyle()
						.getClassName());
	}

	private void unselectYearCell(int row, int col) {
		if (row > LAST_DISPLAYED_YEAR_ROW) {
			return;
		}
		Label l = (Label) yearGrid.getWidget(row, col);
		l
				.removeStyleName(theme.getSelectedYearMonthLabelStyle()
						.getClassName());
	}

	private void clearYearSelection() {
		for (int i = 0; i < yearGrid.getRowCount(); i++) {
			for (int j = 0; j < yearGrid.getColumnCount(); j++) {
				unselectYearCell(i, j);
			}
		}
	}

	@Override
	public void onKeyDown(KeyDownEvent event) {
		SelectionType type = model.getSelectionType();
		if (type != SelectionType.MONTH && type != SelectionType.YEAR) {
			return;
		}
		int keyCode = event.getNativeKeyCode();
		switch (keyCode) {
		case KeyCodes.KEY_LEFT:
			if (type == SelectionType.MONTH) {
				controller.handle(CalendarAction.PREVIOUS_MONTH);
			} else {
				controller.handle(CalendarAction.PREVIOUS_YEAR);
			}
			break;
		case KeyCodes.KEY_RIGHT:
			if (type == SelectionType.MONTH) {
				controller.handle(CalendarAction.NEXT_MONTH);
			} else {
				controller.handle(CalendarAction.NEXT_YEAR);
			}
			break;
		case KeyCodes.KEY_HOME:
			if (type == SelectionType.MONTH) {
				if (model.getDisplayedMonth() == 0) {
					controller.handle(CalendarAction.PREVIOUS_YEAR);
				}
				controller.setDisplayedMonth(0);
				controller.handle(CalendarAction.REFRESH_CALENDAR);
			}
			break;
		case KeyCodes.KEY_END:
			if (type == SelectionType.MONTH) {
				if (model.getDisplayedMonth() == 11) {
					controller.handle(CalendarAction.NEXT_YEAR);
				}
				controller.setDisplayedMonth(11);
				controller.handle(CalendarAction.REFRESH_CALENDAR);
			}
			break;
		}
	}

	@Override
	public void onKeyUp(KeyUpEvent event) {
	}

	@Override
	public void onMouseWheel(MouseWheelEvent event) {
		SelectionType type = model.getSelectionType();
		if (type != SelectionType.MONTH && type != SelectionType.YEAR) {
			return;
		}
		event.preventDefault();
		if (type == SelectionType.MONTH) {
			if (event.isNorth() == true) {
				controller.handle(CalendarAction.PREVIOUS_MONTH);
			} else if (event.isSouth() == true) {
				controller.handle(CalendarAction.NEXT_MONTH);
			}
		} else {
			if (event.isNorth() == true) {
				controller.handle(CalendarAction.PREVIOUS_YEAR);
			} else if (event.isSouth() == true) {
				controller.handle(CalendarAction.NEXT_YEAR);
			}
		}
	}
}